package org.xx.armory.spring5.mvc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.xx.armory.commons.ForLogging;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.PublicKey;

import static java.util.Arrays.stream;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.xx.armory.commons.SignatureUtils.verifyBase64;
import static org.xx.armory.commons.StringConverter.join;
import static org.xx.armory.spring5.ResourceUtils.loadPublicKeyFromPEM;

/**
 * 用于校验请求参数的拦截器。
 *
 * @see WithSignature
 */
public class VerifyRequestParamsSignatureInterceptor
        implements HandlerInterceptor, InitializingBean {
    @ForLogging
    private final Logger logger = LoggerFactory.getLogger(VerifyRequestParamsSignatureInterceptor.class);

    @Value("${armory.mvc.verify-request-params.enabled:false}")
    private boolean enabled;

    @Value("${armory.mvc.verify-request-params.signature-parameter:signature}")
    private String signatureParameter;

    @Value("${armory.mvc.verify-request-params.public-key:classpath:armory-verify-params.key}")
    private Resource publicKeyResource;

    private PublicKey publicKey;

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    )
            throws Exception {
        if (this.enabled && handler instanceof HandlerMethod) {
            final var withSignature = ((HandlerMethod) handler).getMethodAnnotation(WithSignature.class);
            if (withSignature != null) {
                final var arguments = withSignature.arguments();
                if (arguments.length == 0) {
                    logger.warn("Empty arguments of annotation {} of {}, ignored", WithSignature.class, handler);
                    return true;
                }

                logger.trace("Found annotation {} of {}, should verify signature with arguments [{}]", WithSignature.class, handler, join(",", stream(arguments)));
                final var signature = request.getParameter(this.signatureParameter);
                if (isBlank(signature)) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Cannot find parameter " + this.signatureParameter + ", verification failed");
                    return false;
                }

                final var clearText = join("", stream(arguments).map(request::getParameter));
                if (verifyBase64(clearText, signature, this.publicKey)) {
                    logger.trace("Arguments \"{}\" verification with signature \"{}\" passed", clearText, signature);
                } else {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Arguments verification with signature failed");
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public void afterPropertiesSet()
            throws Exception {
        if (this.enabled && this.publicKeyResource != null) {
            this.publicKey = loadPublicKeyFromPEM(this.publicKeyResource);
        }
    }
}
